// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include <fs-management/mount.h>
#include <lib/fdio/io.h>
#include <lib/fdio/spawn.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/directory.h>
#include <lib/zx/process.h>
#include <zircon/compiler.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>

namespace {

void InitArgvAndActions(zx_handle_t* handles, uint32_t* types, size_t len,
                        fdio_spawn_action_t* actions_out) {
  for (size_t i = 0; i < len; ++i) {
    actions_out[i].action = FDIO_SPAWN_ACTION_ADD_HANDLE;
    actions_out[i].h.id = types[i];
    actions_out[i].h.handle = handles[i];
  }
}

constexpr size_t kMaxStdioActions = 1;

enum class StdioType {
  kLog,
  kClone,
  kNone,
};

// Initializes Stdio.
//
// If necessary, updates the |actions| which will be sent to fdio_spawn.
// |action_count| is an in/out parameter which may be increased if an action is
// added.
// |flags| is an in/out parameter which may be modified to alter the cloning of
// STDIO.
void InitStdio(StdioType stdio, fdio_spawn_action_t* actions, size_t* action_count,
               uint32_t* flags) {
  switch (stdio) {
    case StdioType::kLog:
      zx_handle_t h;
      zx_debuglog_create(ZX_HANDLE_INVALID, 0, &h);
      if (h != ZX_HANDLE_INVALID) {
        actions[*action_count].action = FDIO_SPAWN_ACTION_ADD_HANDLE;
        actions[*action_count].h.id = PA_HND(PA_FD, FDIO_FLAG_USE_FOR_STDIO);
        actions[*action_count].h.handle = h;
        *action_count += 1;
      }
      *flags &= ~FDIO_SPAWN_CLONE_STDIO;
      break;
    case StdioType::kClone:
      *flags |= FDIO_SPAWN_CLONE_STDIO;
      break;
    case StdioType::kNone:
      *flags &= ~FDIO_SPAWN_CLONE_STDIO;
      break;
  }
}

enum class ProcessAction {
  kBlock,
  kNonBlock,
};

// Spawns a process.
//
// Optionally blocks, waiting for the process to terminate, depending
// the value provided in |block|.
zx_status_t Spawn(ProcessAction proc_action, uint32_t flags, const char** argv, size_t action_count,
                  const fdio_spawn_action_t* actions) {
  zx::process proc;
  char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
  zx_status_t status = fdio_spawn_etc(ZX_HANDLE_INVALID, flags, argv[0], argv, nullptr,
                                      action_count, actions, proc.reset_and_get_address(), err_msg);
  if (status != ZX_OK) {
    fprintf(stderr, "fs-management: Cannot spawn %s: %d (%s): %s\n", argv[0], status,
            zx_status_get_string(status), err_msg);
    return status;
  }

  if (proc_action == ProcessAction::kBlock) {
    status = proc.wait_one(ZX_PROCESS_TERMINATED, zx::time::infinite(), nullptr);
    if (status != ZX_OK) {
      fprintf(stderr, "spawn: Error waiting for process to terminate\n");
      return status;
    }

    zx_info_process_t info;
    status = proc.get_info(ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr);
    if (status != ZX_OK) {
      fprintf(stderr, "spawn: Failed to get process info\n");
      return status;
    }

    if (!info.exited || info.return_code != 0) {
      return ZX_ERR_BAD_STATE;
    }
  }
  return ZX_OK;
}

zx_status_t Launch(StdioType stdio, ProcessAction proc_action, int argc, const char** argv,
                   zx_handle_t* handles, uint32_t* types, size_t len) {
  fdio_spawn_action_t actions[len + kMaxStdioActions];
  InitArgvAndActions(handles, types, len, actions);

  size_t action_count = len;
  uint32_t flags = FDIO_SPAWN_CLONE_ALL;
  InitStdio(stdio, actions, &action_count, &flags);

  return Spawn(proc_action, flags, argv, action_count, actions);
}

}  // namespace

zx_status_t launch_silent_sync(int argc, const char** argv, zx_handle_t* handles, uint32_t* types,
                               size_t len) {
  return Launch(StdioType::kNone, ProcessAction::kBlock, argc, argv, handles, types, len);
}

zx_status_t launch_silent_async(int argc, const char** argv, zx_handle_t* handles, uint32_t* types,
                                size_t len) {
  return Launch(StdioType::kNone, ProcessAction::kNonBlock, argc, argv, handles, types, len);
}

zx_status_t launch_stdio_sync(int argc, const char** argv, zx_handle_t* handles, uint32_t* types,
                              size_t len) {
  return Launch(StdioType::kClone, ProcessAction::kBlock, argc, argv, handles, types, len);
}

zx_status_t launch_stdio_async(int argc, const char** argv, zx_handle_t* handles, uint32_t* types,
                               size_t len) {
  return Launch(StdioType::kClone, ProcessAction::kNonBlock, argc, argv, handles, types, len);
}

zx_status_t launch_logs_async(int argc, const char** argv, zx_handle_t* handles, uint32_t* types,
                              size_t len) {
  return Launch(StdioType::kLog, ProcessAction::kNonBlock, argc, argv, handles, types, len);
}
